package mnet

/*
 * default big codec.
 */

import (
	"encoding/binary"
	"fmt"
)

// These variables define the length of each part
// of a message in this protocol.
//
// The structure of a message
// is shown in the figure:
//
//	|<-------------- Message Length ------------->|
//
// |<-------- len --------->|<-- message id len -->|<--------- ? -------->|
// +========================+======================+======================+
// |     Message Length     |      Message ID      |         Body         |
// +========================+======================+======================+
const (
	// gMsgDataLen defines the length of variable
	// stored the message length.
	//
	// The length includes the whole message length.
	gMsgDataLen uint32 = 4

	// gMsgIdLen defines the length of variable
	// stored the message id.
	gMsgIdLen uint32 = 2

	// gMaxPackSize defines the maximum length
	// a message could be.
	//
	// The length includes the whole message length.
	gMaxPackSize uint32 = 100 * 1024

	// proto unique object id, 8 bytes.
	gObjIdLen int = 8
)

// BigMsgCodec used to encode message head in big-endian.
type BigMsgCodec struct {
	maxPacketSize uint32
}

// GetHeadLen returns the length of the head,
// which includes the Message Length and Message ID.
func (mc *BigMsgCodec) GetHeadLen() uint32 {
	return gMsgDataLen
}

// PacketParse reads the message head and returns
// the length of Body and Message Id.
// The returned length not includes the message head.
func (mc *BigMsgCodec) PacketParse(msgHead []byte) (uint32, error) {
	dataLen := binary.BigEndian.Uint32(msgHead)
	if mc.maxPacketSize > 0 {
		if dataLen > mc.maxPacketSize {
			return 0, fmt.Errorf("message length(%v) is too large", dataLen)
		}
	}
	return dataLen, nil
}

// BuildProto builds the whole message into bytes with Message Length and Message ID (msgId).
func (mc *BigMsgCodec) BuildProto(msgId uint16, data []byte) []byte {
	dataLen := gMsgIdLen + uint32(len(data))

	// Allocate a buffer for all data (Message Length + Message Id + Body).
	buffer := make([]byte, int(gMsgDataLen+dataLen))

	// write index.
	var writeIndex uint32 = 0

	// Write Message Length into buffer.
	binary.BigEndian.PutUint32(buffer[writeIndex:], dataLen)
	writeIndex += gMsgDataLen

	// Write Message Id into buffer.
	binary.BigEndian.PutUint16(buffer[writeIndex:], msgId)
	writeIndex += gMsgIdLen

	// Copy body data into buffer.
	copy(buffer[writeIndex:], data)

	return buffer
}

// build msgId(4) + objId(8) + message body
func (mc *BigMsgCodec) BuildMessage(msgId uint16, objId uint64, data []byte) []byte {
	dataLen := gMsgIdLen + uint32(gObjIdLen) + uint32(len(data))

	// Allocate a buffer for all data.
	buffer := make([]byte, int(gMsgDataLen+dataLen))

	// write index.
	var writeIndex uint32 = 0

	// Write Message Length into buffer.
	binary.BigEndian.PutUint32(buffer[writeIndex:], dataLen)
	writeIndex += gMsgDataLen

	// Write Message Id into buffer.
	binary.BigEndian.PutUint16(buffer[writeIndex:], msgId)
	writeIndex += gMsgIdLen

	// write object id.
	binary.BigEndian.PutUint64(buffer[writeIndex:], objId)
	writeIndex += uint32(gObjIdLen)

	// Copy Body data into buffer.
	copy(buffer[writeIndex:], data)

	return buffer
}

// SetMaxPacketSize sets the maximum length of a message.
func (mc *BigMsgCodec) SetMaxPacketSize(size uint32) {
	if size > 0 {
		mc.maxPacketSize = size
	}
}

// gMyMsgCodec is the global BigMsgCodec.
var gMyMsgCodec = BigMsgCodec{maxPacketSize: gMaxPackSize}
